/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.model.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/**
 * <p>Content Field (CF) for localizable. Content Field actually represents some field
 * on the page (or any other Localizable) which have values for every (or some)
 * {@link ContentLocale}s
 * (in other words, Content Field represents some localizable content). Those
 * values (which are represented with {@link ContentFieldValue}s) can be
 * edited in edit mode; when not in edit mode, they are just rendered to page.
 * </p>
 * <p>Each Content Field belongs to {@link Localizable}, which is called
 * <b>owner</b>. Content Fields are identified within their owner by
 * <b>identifier</b>. Identifier may be indexed and have form of <em>foo[1.6]</em>.
 * Any <code>double</code> value may be in place of <em>1.6</em>. Set of Content Fields
 * with same base identifier (that is part of identifier before opening square
 * bracket) may be iterated over with &lt;contentIterator&gt; tag.</p>
 * <p>Content Field may be one of three types: <b>LINE_TYPE</b>,
 * <b>MULTILINE_TYPE</b>, <b>HTML_TYPE</b>. First of them defines field which
 * content is single-line text with no formatting; second defines field which
 * content is multiline text with no formatting; and the last stands for a
 * field which stores formatting along with text. Here 'with no formatting'
 * technically means, that any HTML-sensitive characters will be escaped to
 * be displayed as-is, not as HTML-tags, for example.</p>
 * <p>Content Field may be <b>internal</b>. This means that the field is
 * actually a property of some object and cannot be deleted and so on. For
 * example, every {@link Page} has a <b>title</b>, which is internal Content Field.</p>
 * <p><a href="ContentField.java.html"><i>View Source</i></a>
 * </p>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @author Sergey Zubtsovskiy <a href="mailto:sergey.zubtsovskiy@blandware.com">&lt;sergey.zubtsovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.40 $ $Date: 2008/02/18 13:31:12 $
 * @see ContentLocale
 * @see ContentFieldValue
 * @struts.form include-all="false" extends="BaseForm"
 * @hibernate.class table="`al_core_field`" lazy="false"
 */
public class ContentField extends BaseObject {

	/**
	 * Constant for type field. It specifies to use input text to edit this feild.
	 */
	public static final byte LINE_TYPE = 0;

	/**
	 * Constant for type field. It specifies to use textarea to edit this feild.
	 */
	public static final byte MULTILINE_TYPE = 1;

	/**
	 * Constant for type field. It specifies to use HTML editor to edit this feild.
	 */
	public static final byte HTML_TYPE = 2;

	/**
	 * Constant for identifier field. It will be used for <code>TITLE</code> html tag.
	 */
	public static final String TITLE_IDENTIFIER = "title";

	/**
	 * Constant for identifier field. It will be used for <code>META</code> html tag with <code>keywrods</code> name.
	 */
	public static final String KEYWORDS_IDENTIFIER = "keywords";

	/**
	 * Constant for identifier field. It will be used for <code>META</code> html tag with <code>description</code> name.
	 */
	public static final String DESCRIPTION_IDENTIFIER = "description";


	//~ Instance fields ========================================================

    /**
     * The ID of this CF
     */
	protected Long id;
    /**
     * The identifier of this CF. It is unique per field owner.
     */
	protected String identifier;
    /**
     * The identifier of this CF without index.
     */
	protected String identifierWithoutIndex;
    /**
     * The index
     */
	protected Double index;
    /**
     * Type of field: is it line, multiline or html
     */
	protected byte type;
    /**
     * List of content field values that correspond to this field
     */
	protected List contentFieldValues = new ArrayList();
    /**
     * Owner of this field
     */
	protected Localizable owner;
    /**
     * If this field internal
     */
	protected Boolean internal;
    /**
     * Version of this field (used in optimistic locking)
     */
	protected Long version;

	//~ Methods ================================================================

	/**
	 * Returns the id.
	 *
	 * @return id
	 * @struts.form-field
	 * @hibernate.id column="`id`"
	 * generator-class="increment" unsaved-value="null"
	 */
	public Long getId() {
		return id;
	}

    /**
     * Sets the id of CF
     *
     * @param id new id
     */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * Returns version
	 *
	 * @return version
	 * @struts.form-field
	 * @hibernate.version column="`version`" type="long" unsaved-value="null"
	 */
	public Long getVersion() {
		return version;
	}

    /**
     * Sets version of CF
     *
     * @param version new version
     */
	public void setVersion(Long version) {
		this.version = version;
	}

	/**
	 * Returns the identifier of the field
	 *
	 * @return the identifier
	 * @struts.form-field
	 * @struts.validator type="required"
	 * @struts.validator-args arg0resource="core.contentField.form.identifier"
	 * @struts.validator type="mask" msgkey="core.commons.errors.contentFieldIdentifier"
	 * @struts.validator-var name="mask" value="^([0-9a-zA-Z\_]+(\.|\-)?)*[0-9a-zA-Z\_]+$"
	 * @hibernate.property
	 * @hibernate.column name="`identifier`" not-null="true" unique="false" length="252"
	 */
	public String getIdentifier() {
		return identifier;
	}

    /**
     * Sets the identifier of the field
     *
     * @param identifier new identifier
     */
	public void setIdentifier(String identifier) {
		this.identifier = identifier;
		int k = identifier.indexOf('[');
		if ( k != -1 ) {
			index = Double.valueOf(identifier.substring(k + 1, identifier.indexOf(']', k)));
            identifierWithoutIndex = identifier.substring(0, k);
		} else {
			index = null;
            identifierWithoutIndex = identifier;
		}
	}

    /**
     * Returns identifier without index
     *
     * @return pure identifier with no index
     */
    public String getIdentifierWithoutIndex() {
        return identifierWithoutIndex;
    }

	/**
     * Gets the index of the field. If field is not indexed, will return
     * <code>null</code>.
     *
	 * @struts.form-field
	 * @struts.validator type="float"
	 * @struts.validator-args arg0resource="core.contentField.form.index"
	 * @return the index
	 */
	public Double getIndex() {
		return index;
	}

	/**
	 * Returns the list of contentFieldValues dependent on locale.
	 *
	 * @return list of CFVs
	 * @hibernate.bag name="contentFieldValues" inverse="true" lazy="true" cascade="delete" outer-join="false"
	 * @hibernate.collection-key column="`field_id`"
	 * @hibernate.collection-one-to-many class="com.blandware.atleap.model.core.ContentFieldValue"
	 */
	public List getContentFieldValues() {
		return contentFieldValues;
	}

    /**
     * Sets the list of contentFieldValues dependent on locale.
     *
     * @param contentFieldValues list of CFVs
     */
	public void setContentFieldValues(List contentFieldValues) {
		this.contentFieldValues = contentFieldValues;
	}

    /**
     * Adds a content field value to the list of CFVs that are owned by this field
     *
     * @param contentFieldValue the CFV to be added
     */
	public void addContentFieldValue(ContentFieldValue contentFieldValue) {
		contentFieldValue.setContentField(this);
		contentFieldValues.add(contentFieldValue);
	}

    /**
     * Updates ContentFieldValue
     *
     * @param contentFieldValue the value to be updated
     * @return old ContentFieldValue object
     */
	public ContentFieldValue updateContentFieldValue(ContentFieldValue contentFieldValue) {
        ContentFieldValue oldContentFieldValue = null;
		if ( !contentFieldValue.getContentField().equals(this) ) {
            int index = contentFieldValue.getContentField().contentFieldValues.indexOf(contentFieldValue);
            if (index != -1) {
                oldContentFieldValue = (ContentFieldValue)contentFieldValue.getContentField().contentFieldValues.remove(index);
            }
			contentFieldValues.add(contentFieldValue);
			contentFieldValue.setContentField(this);
		}
        return oldContentFieldValue;
	}

    /**
     * Removes a content field value from the list of CFVs that are owned by this field
     *
     * @param contentFieldValue the CFV to be removed
     */
	public void removeContentFieldValue(ContentFieldValue contentFieldValue) {
		contentFieldValues.remove(contentFieldValue);
	}

	/**
	 * Returns map with keys - contentFieldValue locale identifier and values - contentFieldValue
	 *
	 * @return a mapping from locale identifiers to CFVs
	 */
	public Map getContentFieldValuesMap() {
		Collection collection = getContentFieldValues();
		Map map = new HashMap();
		for ( Iterator iterator = collection.iterator(); iterator.hasNext(); ) {
			ContentFieldValue contentFieldValue = (ContentFieldValue) iterator.next();
			map.put(contentFieldValue.getContentLocale().getIdentifier(), contentFieldValue);
		}
		return map;
	}

	/**
	 * Returns the localizable owner
	 *
	 * @return a localizable
	 * @struts.form-field
	 * @hibernate.many-to-one column="`localizable_id`" not-null="true" outer-join="false" lazy="false"
	 */
	public Localizable getOwner() {
		return owner;
	}

    /**
     * Sets the localizable owner
     *
     * @param owner the new owner
     */
	public void setOwner(Localizable owner) {
		this.owner = owner;
	}

	/**
	 * Returns the type of the field
	 *
	 * @return whether this field is single-line, multy-line or html
	 * @struts.form-field
	 * @struts.validator type="required"
	 * @hibernate.property column="`type`" not-null="true" unique="false"
	 */
	public byte getType() {
		return type;
	}

    /**
     * Sets the type of the field
     *
     * @param type type to set
     */
	public void setType(byte type) {
		this.type = type;
	}

	/**
	 * Returns <code>Boolean.TRUE</code> if this content field is internal.
	 * Internal field is created and updated in a different way than other fields.
	 *
	 * @return whether this field is internal
	 * @hibernate.property column="`internal`" not-null="true" type="true_false"
	 */
	public Boolean getInternal() {
		return internal;
	}

    /**
     * Sets whether this field is internal
     *
     * @param internal if <code>Boolean.TRUE</code>, that the field will be
     * internal
     */
	public void setInternal(Boolean internal) {
		this.internal = internal;
	}

	public boolean equals(Object o) {
		if ( this == o ) {
			return true;
		}
		if ( !(o instanceof ContentField) ) {
			return false;
		}

		final ContentField contentField = (ContentField) o;

		if ( identifier != null ? !identifier.equals(contentField.identifier) : contentField.identifier != null ) {
			return false;
		}
		if ( owner != null ? !owner.equals(contentField.owner) : contentField.owner != null ) {
			return false;
		}

		return true;
	}

	public int hashCode() {
		int result;
		result = (identifier != null ? identifier.hashCode() : 0);
		result = 29 * result + (owner != null ? owner.hashCode() : 0);
		return result;
	}

}
